package com.ruoyi.client.service;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.binarywang.wxpay.bean.notify.*;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.google.common.collect.Lists;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.supply.domain.LocationInfo;
import com.ruoyi.supply.domain.SupplyInfo;
import com.ruoyi.supply.domain.TMember;
import com.ruoyi.supply.domain.TMemberConsumeLog;
import com.ruoyi.supply.domain.base.BaseInfoEntity;
import com.ruoyi.supply.domain.dto.ConsumeLongAttachDto;
import com.ruoyi.supply.domain.dto.OrderInfoDto;
import com.ruoyi.supply.domain.dto.WxPayOrderResultDto;
import com.ruoyi.supply.enums.*;
import com.ruoyi.supply.mapper.TMemberConsumeLogMapper;
import com.ruoyi.supply.service.impl.*;
import com.ruoyi.system.domain.SysUserOnline;
import com.ruoyi.system.service.ISysDictTypeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ruoyi.supply.config.Constant.DICT_TYPE;
import static com.ruoyi.supply.config.Constant.MEMBER_GRADE_DICT_TYPE;

/**
 * @author huangxiong
 * @date 2025/06/03 20:44
 */
@Service
@Slf4j

public class ClientUserService {

    @Autowired
    private WxMaService wxMaService;

    @Autowired
    private TMemberService tMemberService;

    @Autowired
    private TMemberConsumeLogMapper tMemberConsumeLogMapper;

    @Autowired
    private MemberLogService memberLogService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private WxPayService wxPayService;

    @Autowired
    private LocationInfoService locationInfoService;

    @Autowired
    private SupplyInfoService supplyInfoService;

    @Autowired
    private ISysDictTypeService dictTypeService;

    @Autowired
    private TInfoImgService tInfoImgService;

    @Autowired
    private RedisCache redisCache;

    public String login(String jsCode){
        WxMaJscode2SessionResult sessionInfo = null;
        try {
            sessionInfo = wxMaService.getUserService().getSessionInfo(jsCode);
        } catch (WxErrorException e) {
            log.error("微信小程序登录异常：{}", e.getMessage(),e);
            throw new BaseException("微信小程序登录异常！！！");
//            sessionInfo = new WxMaJscode2SessionResult();
//            sessionInfo.setOpenid("test:"+ IdUtils.fastSimpleUUID());
//            sessionInfo.setUnionid("test:"+ IdUtils.fastSimpleUUID());
        }
        String openid = sessionInfo.getOpenid();
        final String insertUserLock = "inserUser:lock:"+openid;
        Optional<TMember> tMemberOpt = tMemberService.findByOpenId(openid);
        TMember tMember;
        if(tMemberOpt.isPresent()){
            tMember = tMemberOpt.get();
        }else{
            synchronized (insertUserLock.intern()){

                tMemberOpt = tMemberService.findByOpenId(openid);
                if(!tMemberOpt.isPresent()){
                    log.info("新增用户:{}",openid);
                    // 不存在该用户,新增
                    tMember = initMember(sessionInfo);
                }else{
                    if(log.isDebugEnabled()){
                        log.debug("进入 db check user get:{}",openid);
                    }
                    tMember = tMemberOpt.get();
                }
            }
        }
        Assert.equals(tMember.getStatus(), DfStatusEnum.DF.getCode(),"当前用户已被封禁！请联系管理员");
        // 登陆
        LoginUser loginUser = initLoginUserByMember(tMember);
        return tokenService.createToken(loginUser);

    }

    public String getWxPhone(String code, String encryptedData, String iv) {

        // 1. 通过code获取sessionKey
        WxMaJscode2SessionResult session = null;
        try {
            session = wxMaService.getUserService().getSessionInfo(code);
            String sessionKey = session.getSessionKey();

            // 2. 解密手机号
            WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);
            return phoneNoInfo.getPhoneNumber();
        } catch (WxErrorException e) {
            log.error("获取微信用户手机号失败！userId:{}",SecurityUtils.getUserId(),e);
            throw new IllegalStateException("获取手机号失败！");
        }


    }

    private LoginUser initLoginUserByMember(TMember member){
        LoginUser loginUser = new LoginUser();
        loginUser.setUserId(member.getId());
        loginUser.setCPhone(member.getPhone());
        loginUser.setCGrade(member.getGrade());
        loginUser.setCOpenid(member.getOpenid());
        loginUser.setCMemberExpireTime(member.getExpirationTime());
        loginUser.setIsClient(true);
        return loginUser;
    }

    private TMember initMember(WxMaJscode2SessionResult sessionInfo){
        TMember addMemberDto = new TMember();
        addMemberDto.setOpenid(sessionInfo.getOpenid());
        addMemberDto.setUnionid(sessionInfo.getUnionid());
        addMemberDto.setStatus(DfStatusEnum.DF.getCode());
        addMemberDto.setGrade(MemberGradeEnum.TOURIST.getType());
        addMemberDto.setUsername(MemberGradeEnum.TOURIST.getDesc());
        tMemberService.insertTMember(addMemberDto);
        return addMemberDto;
    }

    private BaseInfoService getInfoService(MemberConsumeTypeEnum memberConsumeTypeEnum){
        return Objects.equals(memberConsumeTypeEnum,MemberConsumeTypeEnum.LOCATION)?locationInfoService:supplyInfoService;
    }

    public OrderInfoDto buyGrade(MemberGradeEnum memberGradeEnum) {
        Assert.notNull(memberGradeEnum,"参数异常，请刷新收重试");
        Assert.notEquals(MemberGradeEnum.TOURIST,memberGradeEnum,"普通会员无需购买！");
        String priceStr = getGradePriceMap().get(memberGradeEnum.name());
        Assert.isTrue(Objects.nonNull(priceStr) && NumberUtil.isNumber(priceStr),"会员价格设置错误！请联系后台！");
        BigDecimal price = new BigDecimal(priceStr);
        Long userId = SecurityUtils.getUserId();
        final String lockKey = "buyGrade:"+userId;
        synchronized (lockKey.intern()){
            // 创建 V3 统一下单请求对象
            WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();

            // 设置基本信息
            request.setAppid(wxPayService.getConfig().getAppId());
            request.setMchid(wxPayService.getConfig().getMchId());
            request.setDescription("购买："+memberGradeEnum.getDesc());
            request.setOutTradeNo(generateOrderNo(MemberConsumeTypeEnum.GRADE.getCode()));
            request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl());

            // 设置金额信息
            WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
            amount.setTotal(price.multiply(new BigDecimal("100")).intValue()); // 金额单位为分
            amount.setCurrency("CNY");
            request.setAmount(amount);

            // 设置支付者信息
            WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
            payer.setOpenid(SecurityUtils.getLoginUser().getCOpenid());
            request.setPayer(payer);

            // 设置场景信息（可选）
            WxPayUnifiedOrderV3Request.SceneInfo sceneInfo = new WxPayUnifiedOrderV3Request.SceneInfo();
            sceneInfo.setPayerClientIp(SecurityUtils.getLoginUser().getIpaddr());
            request.setSceneInfo(sceneInfo);
            request.setAttach(JSONUtil.toJsonStr(ConsumeLongAttachDto.build(null,MemberLogTypeEnum.BUY_MEMBER,memberGradeEnum.getType())));
            try {
                tMemberConsumeLogMapper.insert(
                        TMemberConsumeLog.build(MemberConsumeTypeEnum.GRADE,null,price,userId,request.getOutTradeNo(),memberGradeEnum.getType())
                );
                return new OrderInfoDto(request.getOutTradeNo(),wxPayService.createOrderV3(TradeTypeEnum.JSAPI,request));
            } catch (WxPayException e) {
                log.error("调用微信支付失败！",e);
                throw new IllegalStateException("调用微信支付失败！");
            }
        }
    }
    public OrderInfoDto buyInfo(MemberConsumeTypeEnum memberConsumeTypeEnum, Long infoId) {
        Long userId = SecurityUtils.getUserId();
        final String lockKey = "buyInfo:"+userId;
        synchronized (lockKey.intern()){
            boolean paidMember = SecurityUtils.getLoginUser().isPaidMember();
            if(paidMember){
                throw new IllegalStateException("您已是会员，重新进入程序即可查看");
            }
            BaseInfoEntity entity = getInfoByTypeAndId(memberConsumeTypeEnum,infoId);
            Assert.notNull(entity,  "数据异常，请刷新后重试！");

            boolean isBuy = getInfoService(memberConsumeTypeEnum).isBuy(infoId);
            Assert.isFalse(isBuy,"已购买此内容！");
            // 创建 V3 统一下单请求对象
            WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();

            // 设置基本信息
            request.setAppid(wxPayService.getConfig().getAppId());
            request.setMchid(wxPayService.getConfig().getMchId());
            request.setDescription(entity.getDescription());
            request.setOutTradeNo(generateOrderNo(memberConsumeTypeEnum.getCode()));
            log.debug("创建订单：{}",request.getOutTradeNo());
            request.setNotifyUrl(wxPayService.getConfig().getNotifyUrl());

            // 设置金额信息
            WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
            amount.setTotal(entity.getPrice().multiply(new BigDecimal("100")).intValue()); // 金额单位为分
            amount.setCurrency("CNY");
            request.setAmount(amount);

            // 设置支付者信息
            WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
            payer.setOpenid(SecurityUtils.getLoginUser().getCOpenid());
            request.setPayer(payer);

            // 设置场景信息（可选）
            WxPayUnifiedOrderV3Request.SceneInfo sceneInfo = new WxPayUnifiedOrderV3Request.SceneInfo();
            sceneInfo.setPayerClientIp(SecurityUtils.getLoginUser().getIpaddr());
            request.setSceneInfo(sceneInfo);
            request.setAttach(JSONUtil.toJsonStr(ConsumeLongAttachDto.build(infoId,memberConsumeTypeEnum)));
            try {
                tMemberConsumeLogMapper.insert(TMemberConsumeLog.build(memberConsumeTypeEnum,infoId,entity.getPrice(),userId,request.getOutTradeNo()));
                return new OrderInfoDto(request.getOutTradeNo(),wxPayService.createOrderV3(TradeTypeEnum.JSAPI,request));
            } catch (WxPayException e) {
                log.error("调用微信支付失败！",e);
                throw new IllegalStateException("调用微信支付失败！");
            }
        }

    }
    public String processNotify(String data, HttpServletRequest request) {
        WxPayNotifyV3Result wxPayOrderNotifyResult = null;
        try {
            // 2. 提取请求头中的签名信息
            String signature = request.getHeader("Wechatpay-Signature");
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            // 3. 创建 SignatureHeader 对象
            SignatureHeader header = new SignatureHeader();
            header.setSignature(signature);
            header.setTimeStamp(timestamp);
            header.setNonce(nonce);
            header.setSerial(serialNo);
            wxPayOrderNotifyResult = wxPayService.parseOrderNotifyV3Result(data,header);
            // 获取关键参数（对应 V2 版本的参数）
            WxPayNotifyV3Result.DecryptNotifyResult notifyResult = wxPayOrderNotifyResult.getResult();
//            WxPayOrderResultDto result = buildWxPayOrderResultDto(notifyResult);
            handlePayAfter(notifyResult);
        } catch (Exception e) {
            log.error("processNotify 处理失败:{}",data,e);
            return WxPayNotifyResponse.fail("处理失败!");
        }
        return WxPayNotifyResponse.success("处理成功!");
    }

    public String checkOrderPayStatus(String orderNo) throws WxPayException {
        PayStatusEnum payStatus = memberLogService.getPayStatus(orderNo);
        if(Objects.isNull(payStatus) || PayStatusEnum.FAIL.equals(payStatus)){
            return payStatus.getCode();
        }
        WxPayOrderQueryV3Result wxPayOrderQueryV3Result = wxPayService.queryOrderV3(null, orderNo);
        String tradeState = wxPayOrderQueryV3Result.getTradeState();
        if(StrUtil.equals("SUCCESS",tradeState)){
            handlePayAfter(wxPayOrderQueryV3Result);
            return PayStatusEnum.SUCCESS.getCode();
        }else if(StrUtil.equalsAny(tradeState,"PAYERROR","CLOSED","REVOKED","NOTPAY","REFUND")){
            // 支付失败！
            memberLogService.updatePayStatus(orderNo,PayStatusEnum.FAIL,wxPayOrderQueryV3Result.getTradeStateDesc());
            return PayStatusEnum.FAIL.getCode();
        }else{
            return PayStatusEnum.ING.getCode();
        }
    }

    public List<Object> selectMyPush(String infoType) {
        SupplyInfoTypeEnum supplyInfoTypeEnum = SupplyInfoTypeEnum.getByCode(infoType);
        Assert.notNull(supplyInfoTypeEnum,"数据异常，请刷新后重试！");
        BaseInfoService baseInfoService = supplyInfoTypeEnum.equals(SupplyInfoTypeEnum.SOURCE)?supplyInfoService:locationInfoService;
        BaseInfoEntity entity = supplyInfoTypeEnum.equals(SupplyInfoTypeEnum.SOURCE)?new SupplyInfo():new LocationInfo();
        entity.setCreateBy(SecurityUtils.getUserId().toString());
        return baseInfoService.selectInfoList(entity);
    }

    public List selectMyOrder(String type,@Nullable String param,String status) {
        Long userId = SecurityUtils.getUserId();
        if(Objects.equals(type,MemberConsumeTypeEnum.GRADE.getCode())){
            // 查找会员购买记录
            return memberLogService.getGradeConsumeRecord(userId,status);
        }else{
            List<BaseInfoEntity> result =  memberLogService.getInfoConsumeRecord(userId,param,status);
            if(CollUtil.isNotEmpty(result)){
                result.forEach(it->{
                    it.setImgList(tInfoImgService.findImgByType(it.getId(),it.getType()));
                });
            }
            return result;
        }
    }


    /**
     *  定时任务执行
     */
    public void refreshOrderStatus(){
        List<String> orderNoList =memberLogService.getIngPayOrderNo();
        if(CollUtil.isNotEmpty(orderNoList)){
            orderNoList.forEach(it-> {
                try {
                    checkOrderPayStatus(it);
                }catch (WxPayException e) {
                    if(StrUtil.equals(e.getMessage(),"订单不存在")){
                        memberLogService.updatePayStatus(it,PayStatusEnum.FAIL,e.getMessage());
                    }
                    //do nothing
                    log.error("订单支付状态查询异常:{}",it,e);
                }
            });
        }
    }

    public void refreshMemberStatus(){
        tMemberService.refreshOverTimeMember();
    }

    private BaseInfoEntity getInfoByTypeAndId(MemberConsumeTypeEnum memberConsumeTypeEnum,Long infoId){
        BaseInfoEntity entity;
        if(Objects.equals(memberConsumeTypeEnum, MemberConsumeTypeEnum.SOURCE)){
            entity = supplyInfoService.getById(infoId);
        }else{
            entity = locationInfoService.getById(infoId);
        }
        return entity;
    }

    /**
     * 生成包含商品类型的订单号
     * @param productType 商品类型（例如：MEMBERSHIP、COURSE、GOODS等）
     * @return 格式化的订单号
     */
    private synchronized String generateOrderNo(String productType) {
        // 时间戳部分（精确到毫秒）
        String timestamp = String.valueOf(System.currentTimeMillis());

        // 随机数部分（3位）
        int randomNum = (int) (Math.random() * 900) + 100;

        // 组合成完整订单号
        return String.format("%s%s%d", productType+0, timestamp, randomNum);
    }
    private Map<String, String> getGradePriceMap(){
        List<SysDictData> sysDictData = dictTypeService.selectDictDataByType(MEMBER_GRADE_DICT_TYPE);
        Map<String, String> dictData = sysDictData.stream()
                .collect(Collectors.toMap(
                        SysDictData::getDictLabel,  // key 映射
                        SysDictData::getDictValue, // value 映射
                        (existing, replacement) -> existing // 处理键冲突的合并函数
                ));
        return dictData;
    }

    private WxPayOrderResultDto buildWxPayOrderResultDto(Object obj) {
       log.info("支付信息："+obj);
        WxPayOrderResultDto result = BeanUtil.copyProperties(obj, WxPayOrderResultDto.class,"goodsDetails","promotionDetails","payer","amount");
        if(ReflectUtil.hasField(obj.getClass(),"payer")){
            result.setPayer(BeanUtil.copyProperties(ReflectUtil.getFieldValue(obj,"payer"), WxPayOrderResultDto.Payer.class));
        }
        if(ReflectUtil.hasField(obj.getClass(),"amount")){
            result.setAmount(BeanUtil.copyProperties(ReflectUtil.getFieldValue(obj,"amount"), WxPayOrderResultDto.Amount.class));
        }
        return result;
    }
    private void handlePayAfter(Object obj){
        log.info("回调订单：{}",JSONUtil.toJsonStr(obj));
        WxPayOrderResultDto result = buildWxPayOrderResultDto(obj);
        String outTradeNo = result.getOutTradeNo();
        String orderLockKey = "orderLock:"+outTradeNo;
        PayStatusEnum payStatus = memberLogService.getPayStatus(outTradeNo);
        if(Objects.nonNull(payStatus) && !payStatus.equals(PayStatusEnum.ING)){
            log.info("当前orderNo:{}，已经处理，状态为:{}",outTradeNo,payStatus.getDesc());
            return;
        }
        synchronized (orderLockKey.intern()){
            try{
                payStatus = memberLogService.getPayStatus(outTradeNo);
                if(Objects.nonNull(payStatus) && !payStatus.equals(PayStatusEnum.ING)){
                    log.info("当前orderNo:{}，已经处理，状态为:{}",outTradeNo,payStatus.getDesc());
                    return;
                }
                String openid = null;
                if(isLogin()){
                    openid = SecurityUtils.getLoginUser().getCOpenid();
                }else{
                    openid = result.getPayer().getOpenid();
                }
                String transactionId = result.getTransactionId();        // 微信支付订单号
                String tradeType = result.getTradeType();                // 交易类型
                String tradeState = result.getTradeState();              // 交易状态
                log.info(result.toString());
                // 获取金额信息（V3 以元为单位，需转换为分）
                Integer totalFee = result.getAmount().getTotal();        // 订单总金额（分）

                // V3 中没有直接对应的 attach 字段，但可以通过 metadata 传递自定义数据
                String attach = result.getAttach();                     // 商户附加数据（需在下单时设置
                ConsumeLongAttachDto consumeLongAttachDto = JSONUtil.toBean(attach, ConsumeLongAttachDto.class);
                MemberLogTypeEnum typeEnum = MemberLogTypeEnum.getByCode(consumeLongAttachDto.getBuyType());
                Assert.notNull(typeEnum,"获取订单类型失败！");
                Optional<TMember> tMemberOpt = tMemberService.findByOpenId(openid);
                Assert.isTrue(tMemberOpt.isPresent(),"当前openid无法找到用户！");
                TMember tMember = tMemberOpt.get();
                MemberConsumeTypeEnum consumeTypeEnum = null;
                switch (typeEnum){
                    case BUY_LOCATION:
                        consumeTypeEnum = MemberConsumeTypeEnum.LOCATION;
                    case BUY_SUPPLY:
                        consumeTypeEnum = Objects.isNull(consumeTypeEnum)?MemberConsumeTypeEnum.SOURCE:consumeTypeEnum;
                        BaseInfoEntity infoEntity = getInfoByTypeAndId(consumeTypeEnum, consumeLongAttachDto.getIdAsLong());
                        Assert.notNull(typeEnum,"获取购买信息失败！");
                        Assert.equals(infoEntity.getPrice().multiply(new BigDecimal(100)).intValue() , totalFee,"支付费用不匹配！");
//                    tMemberConsumeLogMapper.insert(TMemberConsumeLog.build(consumeTypeEnum,infoEntity.getId(),infoEntity.getPrice(),tMember.getId(),outTradeNo));
                        memberLogService.updatePayStatus(outTradeNo, PayStatusEnum.SUCCESS,null);
                        break;
                    case BUY_MEMBER:
                        String grade = consumeLongAttachDto.getAttach();
//                        consumeTypeEnum = MemberConsumeTypeEnum.GRADE;
                        MemberGradeEnum memberGradeEnum = MemberGradeEnum.getByCode(grade);
                        TMember plusData = tMemberService.plusMemberTime(tMember.getId(), memberGradeEnum);
                        memberLogService.updatePayStatus(outTradeNo, PayStatusEnum.SUCCESS,StrUtil.format("购买会员成功，等级:{},当前会员最新的失效时间为:{}",memberGradeEnum.getDesc(),plusData.genExpirationTimeStr()));
                        break;
                }
            }catch (Exception e){
                log.error("处理微信支付结果异常",e);
                memberLogService.updatePayStatus(outTradeNo, PayStatusEnum.FAIL,e.getMessage());
            }

        }
    }



    private boolean isLogin(){
        try{
            return SecurityUtils.getLoginUser() != null;
        }catch (Exception e){
            return false;
        }
    }

}
